using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;
using Microsoft.Xna.Framework.Graphics;

namespace Deferred.Pipeline
{
    /// <summary>
    /// This class will be instantiated by the XNA Framework Content Pipeline
    /// to apply custom processing to content data, converting an object of
    /// type TInput to TOutput. The input and output types may be the same if
    /// the processor wishes to alter data without changing its type.
    ///
    /// This should be part of a Content Pipeline Extension Library project.
    ///
    /// TODO: change the ContentProcessor attribute to specify the correct
    /// display name for this processor.
    /// </summary>
    [ContentProcessor(DisplayName = "Deferred.Pipeline.DeferredModelProcessor")]
    public class DeferredModelProcessor : ContentProcessor<NodeContent, DeferredModelContent>
    {
        ContentProcessorContext context;
        DeferredModelContent outputModel;
        TextureProcessor textureProcessor = new TextureProcessor();
        static IList<string> acceptableVertexChannelNames = new string[] {
            VertexChannelNames.TextureCoordinate(0),
            VertexChannelNames.Normal(0),
            VertexChannelNames.Binormal(0),
            VertexChannelNames.Tangent(0)
        };
        string directory;

        [DisplayName("Normal Map Texture")]
        [Description("If set, this file will be used as the normal map on the model, overriding anything found in the opaque data.")]
        [DefaultValue("")]
        public string NormalMapTexture
        {
            get { return normalMapTexture; }
            set { normalMapTexture = value; }
        }
        private string normalMapTexture;

        [DisplayName("Normal Map Key")]
        [Description("This will be the key that will be used to search the normal map in the opaque data of the model")]
        [DefaultValue("NormalMap")]
        public string NormalMapKey
        {
            get { return normalMapKey; }
            set { normalMapKey = value; }
        }
        private string normalMapKey = "NormalMap";

        [DisplayName("Specular Map Texture")]
        [Description("If set, this file will be used as the specular map on the model, overriding anything found in the opaque data.")]
        [DefaultValue("")]
        public string SpecularMapTexture
        {
            get { return specularMapTexture; }
            set { specularMapTexture = value; }
        }
        private string specularMapTexture;

        [DisplayName("Specular Map Key")]
        [Description("This will be the key that will be used to search the specular map in the opaque data of the model")]
        [DefaultValue("SpecularMap")]
        public string SpecularMapKey
        {
            get { return specularMapKey; }
            set { specularMapKey = value; }
        }
        private string specularMapKey = "SpecularMap";

        [DisplayName("Texture Format")]
        [Description("Specifies the SurfaceFormat type of processed textures.  Textures can either remain unchanged from the source asset, converted to the Color format, or DXT compressed.")]
        public TextureProcessorOutputFormat TextureFormat
        {
            get
            {
                return textureProcessor.TextureFormat;
            }
            set
            {
                textureProcessor.TextureFormat = value;
            }

        }

        [DisplayName("Resize Textures to Power of Two")]
        [Description("If enabled, the model's textures are resized to the next largest power of two, maximizing compatibility.  Many graphics cards do not support texture sizes that are not a power of two.")]
        public bool ResizeToPowerOfTwo
        {
            get
            {
                return textureProcessor.ResizeToPowerOfTwo;
            }
            set
            {
                textureProcessor.ResizeToPowerOfTwo = value;
            }
        }

        [DisplayName("Generate Mipmaps")]
        [Description("If enabled, a full mipmap chain is generated for the model's textures.  Existing mipmaps are not replaced.")]
        public bool GenerateMipmaps
        {
            get
            {
                return textureProcessor.GenerateMipmaps;
            }
            set
            {
                textureProcessor.GenerateMipmaps = value;
            }
        }

        public override DeferredModelContent Process(NodeContent input, ContentProcessorContext context)
        {
            this.context = context;

            outputModel = new DeferredModelContent();

            directory = Path.GetDirectoryName(input.Identity.SourceFilename);
            ProcessNode(input);

            return outputModel;
        }

        /// <summary>
        /// Recursively processes a node from the input data tree.
        /// </summary>
        void ProcessNode(NodeContent node)
        {
            // Meshes can contain internal hierarchy (nested tranforms, joints, bones,
            // etc), but this sample isn't going to bother storing any of that data.
            // Instead we will just bake any node transforms into the geometry, after
            // which we can reset them to identity and forget all about them.
            MeshHelper.TransformScene(node, node.Transform);

            node.Transform = Matrix.Identity;

            // Is this node in fact a mesh?
            MeshContent mesh = node as MeshContent;

            if (mesh != null)
            {
                DeferredModelMeshContent modelMesh = new DeferredModelMeshContent();
                setNormalMapName(mesh, modelMesh);
                setSpecularMapName(mesh, modelMesh);
                // Reorder vertex and index data so triangles will render in
                // an order that makes efficient use of the GPU vertex cache.
                MeshHelper.OptimizeForCache(mesh);

                // Process all the geometry in the mesh.
                foreach (GeometryContent geometry in mesh.Geometry)
                {
                    modelMesh.meshParts.Add(ProcessGeometry(geometry, modelMesh));
                }

                outputModel.meshes.Add(modelMesh);
            }

            // Recurse over any child nodes.
            foreach (NodeContent child in node.Children)
            {
                ProcessNode(child);
            }
        }

        private void setSpecularMapName(MeshContent mesh, DeferredModelMeshContent modelMesh)
        {
            String specularMapName = "";

            //If the SpecularMapTexture property is set, we use it
            if (!String.IsNullOrEmpty(SpecularMapTexture))
            {
                specularMapName = SpecularMapTexture;
            }
            else
            {
                //If SpecularMapTexture is not set, we look into the opaque data of the model, 
                //and search for a texture with the key equal to specularMapKey
                specularMapName = mesh.OpaqueData.GetValue<string>(specularMapKey, null);
            }

            modelMesh.SpecularMapName = specularMapName;
        }

        private void setNormalMapName(MeshContent mesh, DeferredModelMeshContent modelMesh)
        {
            //this will contatin the path to the normal map texture
            String normalMapName = "";

            //If the NormalMapTexture property is set, we use that normal map for all meshes in the model.
            //This overrides anything else
            if (!String.IsNullOrEmpty(NormalMapTexture))
            {
                normalMapName = NormalMapTexture;
            }
            else
            {
                //If NormalMapTexture is not set, we look into the opaque data of the model, 
                //and search for a texture with the key equal to NormalMapKey
                normalMapName = mesh.OpaqueData.GetValue<string>(NormalMapKey, null);
            }

            modelMesh.NormalMapName = normalMapName;
        }

        /// <summary>
        /// Converts a single piece of input geometry into our custom format.
        /// </summary>
        DeferredModelMeshPartContent ProcessGeometry(GeometryContent geometry, DeferredModelMeshContent parent)
        {
            DeferredModelMeshPartContent meshPart = new DeferredModelMeshPartContent();

            meshPart.TriangleCount = geometry.Indices.Count / 3;
            meshPart.VertexCount = geometry.Vertices.VertexCount;

            // Flatten the flexible input vertex channel data into
            // a simple GPU style vertex buffer byte array.
            VertexBufferContent vertexBufferContent;
            VertexElement[] vertexElements;

            geometry.Vertices.CreateVertexBuffer(out vertexBufferContent,
                                                 out vertexElements,
                                                 context.TargetPlatform);
            meshPart.VertexBufferContent = vertexBufferContent;
            meshPart.VertexElements = vertexElements;

            meshPart.VertexStride = VertexDeclaration.GetVertexStrideSize(vertexElements, 0);
            meshPart.IndexCollection = geometry.Indices;

            meshPart.TextureName = getTextureName(geometry, "Texture");
            meshPart.NormalMapName = string.IsNullOrEmpty(parent.NormalMapName) ? getTextureName(geometry, normalMapKey) : Path.GetFileNameWithoutExtension(parent.NormalMapName);
            meshPart.SpecularMapName = string.IsNullOrEmpty(parent.SpecularMapName) ? getTextureName(geometry, specularMapKey) : Path.GetFileNameWithoutExtension(parent.SpecularMapName);

            return meshPart;
        }

        private string getTextureName(GeometryContent geometry, string key)
        {
            if (geometry.Material.Textures.ContainsKey(key))
            {
                return Path.GetFileNameWithoutExtension(geometry.Material.Textures[key].Filename);
            }
            else
                return "";
        }
    }
}